package de.tsajar.es.networking.server;

import com.jme3.app.Application;
import com.jme3.app.state.AppStateManager;
import com.jme3.network.HostedConnection;
import com.jme3.network.Message;
import com.jme3.network.MessageListener;
import com.jme3.network.Server;
import com.jme3.network.serializing.Serializer;
import com.simsilica.es.ComponentFilter;
import com.simsilica.es.Entity;
import com.simsilica.es.EntityData;
import com.simsilica.es.EntitySet;
import com.simsilica.es.filter.AndFilter;
import com.simsilica.es.filter.FieldFilter;
import com.simsilica.es.filter.OrFilter;
import de.tsajar.es.EntitySystemAppState;
import de.tsajar.es.networking.messages.EntityChangeMessage;
import de.tsajar.es.networking.serializer.ClassSerializer;
import de.tsajar.es.networking.messages.EntitySetResponseMessage;
import de.tsajar.es.networking.messages.RequestEntitySetMessage;
import de.tsajar.es.networking.serializer.EntitySystemSerializer;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Host for remote EntitySet queries. Can simply be added to an existing ES,
 * only has to be called frequently to send updates.
 *
 * @author Eike Foede
 */
public class EntityDataServerState extends EntitySystemAppState implements MessageListener<HostedConnection> {

    private Map<AtomicLong, EntitySet> entitySets = new ConcurrentHashMap<AtomicLong, EntitySet>();
    private Map<AtomicLong, HostedConnection> clients = new ConcurrentHashMap<AtomicLong, HostedConnection>();
    //Id counter for assigning EntitySets ids
    private AtomicLong idCounter = new AtomicLong(0);
    Server server;
    private float tpUpdate = 0.0000001f;
    private float tpfCount = 0;
    private AtomicLong time = new AtomicLong(0);

    public EntityDataServerState(Server server) {
        this.server = server;
        Serializer.registerClasses(EntityChangeMessage.class,
                EntitySetResponseMessage.class,
                RequestEntitySetMessage.class);
        Serializer s = new EntitySystemSerializer();
        Serializer.registerClass(Entity.class, s);
        Serializer.registerClass(FieldFilter.class, s);
        Serializer.registerClass(OrFilter.class, s);
        Serializer.registerClass(AndFilter.class, s);
        Serializer.registerClass(Class.class, new ClassSerializer());
    }

    @Override
    public void initialize(AppStateManager stateManager, Application app) {
        super.initialize(stateManager, app);
        server.addMessageListener(this, RequestEntitySetMessage.class);
    }

    @Override
    public void update(float tpf) {
        tpfCount += tpf;
        if (tpfCount > tpUpdate) {
            tpfCount %= tpUpdate;
            time.incrementAndGet();
            Set<Map.Entry<AtomicLong, EntitySet>> entrySet = entitySets.entrySet();
            //Iterate through all EntitySets
            for (Entry<AtomicLong, EntitySet> entry : entrySet) {
                EntitySet set = entry.getValue();
                AtomicLong id = entry.getKey();
                if (set.applyChanges()) {//If EntitySet has changed
                    EntityChangeMessage ecm = new EntityChangeMessage(id.longValue(), time.get(), set.getAddedEntities(), set.getChangedEntities(), set.getRemovedEntities());
                    ecm.setReliable(true);
                    //Send changes to assigned client
                    clients.get(id).send(ecm);
                }
            }
        }
    }

    @Override
    public void cleanup() {
        super.cleanup();
    }

    /**
     * Creates a new EntitySet
     *
     * @param filter
     * @param types
     * @return The id of the created EntitySet
     */
    public AtomicLong getEntities(ComponentFilter filter, Class... types) {
        EntitySet es = ed.getEntities(filter, types);
        AtomicLong id = this.idCounter;
        this.idCounter.incrementAndGet();
        entitySets.put(id, es);
        return id;
    }

    @Override
    public void messageReceived(HostedConnection s, Message msg) {
        if (msg instanceof RequestEntitySetMessage) {//New EntitySet is requested
            RequestEntitySetMessage rq = (RequestEntitySetMessage) msg;
            AtomicLong id = getEntities(rq.f, rq.types);
            clients.put(id, s);
            s.send(new EntitySetResponseMessage(id.longValue(), rq.requestId));
            Set<Entity> entities = new HashSet<Entity>();
            for (Entity e : entitySets.get(id)) {
                entities.add(e);
            }
            s.send(new EntityChangeMessage(id.longValue(), time.get(),
                    entities, new HashSet<Entity>(), new HashSet<Entity>()).setReliable(true));
        }
    }
}
